package se.webbzon.oschi01.worldcamera;

import java.awt.Dimension;
import java.util.Enumeration;

import javax.media.j3d.Behavior;
import javax.media.j3d.BoundingSphere;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.WakeupCriterion;
import javax.media.j3d.WakeupOnElapsedTime;
import javax.vecmath.Point3d;

import se.webbzon.oschi01.worldarea.WorldAppearance3D;
import se.webbzon.oschi01.worldarea.WorldArea;
import se.webbzon.oschi01.worldarea.WorldEngine;
import se.webbzon.oschi01.worldarea.WorldEngineException;
import se.webbzon.oschi01.worldarea.WorldEngineListener;
import se.webbzon.oschi01.worldarea.WorldEngineTimer;

public class SimpleWorldRenderer implements WorldEngineTimer {
	
	// The singleton instance of this class
	private static SimpleWorldRenderer singleton = null;
	
	// The rendering behavior of this renderer
	private final BranchGroup bbg;
	private final BehaviorImpl behavior;

	// The world engine listener of this renderer
	private final WorldEngineListenerImpl listener;
	
	// The appearance of the world attached to this renderer
	private final WorldAppearance3D appearance;

	// The minimal time between rendering
	private long milliseconds;
	
	// The simple world view of this renderer
	private SimpleWorldView view; 

	// Set to true when the world need to be repainted
	private boolean paused = false;

	// The WorldEngine attached to this renderer
	private WorldEngine engine;

	/*============================================================
	Constructors
	============================================================*/
	
	public SimpleWorldRenderer() {
		milliseconds = 25;
		bbg = new BranchGroup();
		behavior = new BehaviorImpl();
		bbg.addChild(behavior);
		bbg.setCapability(BranchGroup.ALLOW_DETACH);
		bbg.compile();
		listener = new WorldEngineListenerImpl();
		appearance = new WorldAppearance3D(0); // WorldAppearance3D.RENDERING_ORDER
	}

	/*============================================================
	Public Methods
	============================================================*/
	
	public static final SimpleWorldRenderer instance() {
		if (singleton == null) {
			singleton = new SimpleWorldRenderer();
		}
		return singleton;
	}
	
	public final WorldView getWorldView() {
		return view;
	}
	
	public final AbstractWorldView openWorldView(Dimension d) throws WorldViewException {
		if (view == null) {
			SimpleAWTWorldView v = new SimpleAWTWorldView(WorldView.TRANSPARENCY,d);
			view = v;
			view.setSymbiosis(new Symbiosis());
			v.getUniverse().addBranchGraph(appearance.getJ3dBranch());
			v.getUniverse().addBranchGraph(bbg);
			return v;
		} else 
			throw new WorldViewException("A world view is already open.");
	}
	
	public final AbstractJWorldView openJWorldView(Dimension d) throws WorldViewException {
		if (view == null) {
			SimpleJWorldView v = new SimpleJWorldView(WorldView.TRANSPARENCY,d);
			view = v;
			view.setSymbiosis(new Symbiosis());
			v.getUniverse().addBranchGraph(appearance.getJ3dBranch());
			v.getUniverse().addBranchGraph(bbg);
			return v;
		} else 
			throw new WorldViewException("A world view is already open.");
	}
	
	public final void closeWorldView() {
		if (view != null) {
			appearance.getJ3dBranch().detach();
			bbg.detach();
			view.setSymbiosis(null);
			//view.getUniverse().cleanup();
			view = null;
		}
	}
	
	@Override public boolean attachEngine(WorldEngine engine) throws WorldEngineException {
		if (this.engine != null) 
			throw new WorldEngineException("A world engine is already attached to this world engine timer.");
		else if (engine != null) {
			engine.addListener(listener);
			appearance.setWorld(engine.getWorld());
			this.engine = engine;
			return true;
		} else
			return false;
	}

	@Override public boolean detachEngine(WorldEngine engine) throws WorldEngineException {
		if (this.engine != engine)
			throw new WorldEngineException("The provided engine is not attached to this world engine timer.");
		else if (this.engine != null) {
			this.engine.removeListener(listener);
			this.engine = null;
			return true;
		} else
			return false;
	}

	@Override public void setPaused(boolean paused) {
		this.paused = paused;
	}

	@Override public boolean getPaused() {
		return paused;
	}

	/** Returns the world engine of this world renderer. **/
	public WorldEngine getEngine() {
		return engine;
	}

	/*============================================================
	Private Classes
	============================================================*/
	
	private final class Symbiosis implements WorldViewSymbiosis {

		@Override public void preRender(WorldView host) {
			WorldAppearance3D a = appearance;
			if (a != null)
				host.preRender(a);
		}

		@Override public void postRender(WorldView host) {
			WorldAppearance3D a = appearance;
			if (a != null)
				host.postRender(a);
		}
		
	}

	/** The behavior of this world renderer. **/
	private final class BehaviorImpl extends Behavior {

		private final WakeupCriterion c = new WakeupOnElapsedTime(milliseconds);

		public BehaviorImpl() {
			setSchedulingBounds(new BoundingSphere(new Point3d(), Double.MAX_VALUE));
		}

		@Override public void initialize() {
			wakeupOn(c);
		}

		@SuppressWarnings("rawtypes")
		@Override public void processStimulus(Enumeration arg0) {
			if (!paused) {
				engine.performStep();
			}
			appearance.performDraw();
			wakeupOn(c);
		}

	}

	/** An implementation of a world engine listener. **/
	private final class WorldEngineListenerImpl implements WorldEngineListener {

		@Override public void onSetWorldArea(WorldEngine engine, WorldArea world) {
			appearance.setWorld(world);
		}
		
		@Override public void onStepStartup(WorldEngine engine, WorldArea world) {};

		@Override public void onStepComplete(WorldEngine engine, WorldArea world) {};

	}

}
